package com.basker.pisces.orm.query;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.springframework.util.StringUtils;

import com.basker.pisces.core.enums.TableType;
import com.basker.pisces.core.query.ICriteria;
import com.basker.pisces.core.query.criteria.Criterion;
import com.basker.pisces.core.query.criteria.iterate.CriteriaIterator;
import com.basker.pisces.core.query.criteria.iterate.CriterionIterateArgs;
import com.basker.pisces.core.query.criteria.iterate.ICriteriaIterateHandler;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.query.meta.TableDesc;

public class WhereClausesGenerator implements ICriteriaIterateHandler {
    public static String genWhereClauses(QueryMeta info, List<Criterion> criteria) {
        CriteriaIterator iterator = new CriteriaIterator(info);
        WhereClausesGenerator generator = new WhereClausesGenerator();

        iterator.iterate(criteria, generator);

        return generator.whereClauseStack.peek().toString();
    }

    private Stack<StringBuilder> whereClauseStack = new Stack<StringBuilder>();

    private WhereClausesGenerator() {
        whereClauseStack.add(new StringBuilder());
    }

    @Override
    public void beginIterate(CriterionIterateArgs args) {
        Criterion criterion = args.getCriterion();

        StringBuilder whereClause = whereClauseStack.peek();

        if (criterion.isBeginBracket()) {
            whereClause.append("(");
        }

        if (criterion instanceof ICriteria) {
            if (!((ICriteria) criterion).isEmpty()) {
                whereClauseStack.push(new StringBuilder());
            }
        }
    }

    @Override
    public void endIterate(CriterionIterateArgs args) {
        Criterion criterion = args.getCriterion();

        String condition = this.genCondition(args);
        if (criterion.isExist()) {
            condition = this.genExistClause(args, condition);
        }

        StringBuilder whereClause = whereClauseStack.peek();

        if (!StringUtils.isEmpty(condition)) {
            whereClause.append(condition);
        }

        if (criterion.isEndBracket()) {
            whereClause.append(")");
        }

        if (!args.isLast()) {
            whereClause.append(MessageFormat.format(" {0} ", criterion.getAndOr()));
        }
    }

    private void appendJoinClauses(StringBuilder sbFrom, TableDesc joinTableDesc, TableDesc preTableDesc) {
        sbFrom.append(
                MessageFormat.format(" {0} {1} {2} ", "LEFT JOIN", joinTableDesc.getName(), joinTableDesc.getAlias()));
        sbFrom.append("ON");

        String preTableFullFieldName = null;
        String joinTableFullFiledName = null;

        TableType tableType = joinTableDesc.getType();

        if (TableType.Sub.equals(tableType) || TableType.Child.equals(tableType)) {
            preTableFullFieldName = preTableDesc.getPkFullFieldName();
            joinTableFullFiledName = joinTableDesc.getFkFullFieldName();
        } else if (TableType.Reference.equals(tableType)) {
            preTableFullFieldName = preTableDesc.getFullFieldName(joinTableDesc.getFkFieldName());
            joinTableFullFiledName = joinTableDesc.getPkFullFieldName();
        }

        sbFrom.append(MessageFormat.format(" ({0} = {1}) ", preTableFullFieldName, joinTableFullFiledName));
    }

    private String genCondition(CriterionIterateArgs args) {
        Criterion criterion = args.getCriterion();

        if (criterion instanceof ICriteria) {
            if (!((ICriteria) criterion).isEmpty()) {
                return whereClauseStack.pop().toString();
            }
        } else {
            TableDesc hostTable = args.getHostTable();
            return criterion.getCondition(hostTable.getAlias());
        }

        return null;
    }

    private String genExistClause(CriterionIterateArgs args, String condition) {
        Criterion criterion = args.getCriterion();
        TableDesc hostTable = args.getHostTable();
        TableDesc rootTable = this.getRootTable(args, criterion);

        return this.getExistCondition(hostTable, rootTable, condition);
    }

    private String getExistCondition(List<TableDesc> fromTables, String originCondition) {
        int size = fromTables.size();
        StringBuilder joinClauses = new StringBuilder(size * 60);

        TableDesc hostTable = fromTables.get(0);
        TableDesc parentTable = hostTable.getParentTable();

        joinClauses.append(hostTable.getName()).append(" ").append(hostTable.getAlias());

        for (int i = 1; i < fromTables.size(); i++) {
            TableDesc joinTable = fromTables.get(i);
            TableDesc preTable = joinTable.getParentTable();

            appendJoinClauses(joinClauses, joinTable, preTable);
        }

        String parentTablePkField = parentTable.getPkFullFieldName();
        String hostTableFkField = hostTable.getFkFullFieldName();

        return MessageFormat.format("EXISTS (SELECT 1 FROM {0} WHERE {1} = {2} AND ({3}))", joinClauses,
                parentTablePkField, hostTableFkField, originCondition);
    }

    private String getExistCondition(TableDesc hostTable, TableDesc rootTable, String originCondition) {
        if (rootTable == hostTable) {
            return originCondition;
        }

        List<TableDesc> fromTables = new ArrayList<TableDesc>();
        fromTables.add(0, hostTable);

        TableDesc parentTable = hostTable.getParentTable();
        while (true) {
            if (parentTable.getLevel() != hostTable.getLevel()) {
                break;
            }

            fromTables.add(0, parentTable);

            hostTable = parentTable;
            parentTable = parentTable.getParentTable();

        }

        originCondition = this.getExistCondition(fromTables, originCondition);

        if (rootTable == null) {
            return originCondition;
        }

        return this.getExistCondition(parentTable, rootTable, originCondition);
    }

    private TableDesc getRootTable(CriterionIterateArgs args, Criterion exp) {
        TableDesc rootTable = null;

        if (rootTable == null) {
            String existRootTableName = exp.getExistRootTableName();
            if (StringUtils.isEmpty(existRootTableName)) {
                rootTable = args.getQueryMeta().getMainTable();
            } else {
                rootTable = args.getQueryMeta().getTable(existRootTableName);

                if (rootTable == null) {
                    TableDesc hostTable = args.getHostTable();
                    while (true) {
                        if (hostTable != null && hostTable.getName().equals(existRootTableName)) {
                            rootTable = hostTable;
                            break;
                        }

                        hostTable = hostTable.getParentTable();
                        if (hostTable == null) {
                            break;
                        }
                    }
                }
            }
        }
        return rootTable;
    }
}
